﻿using System;
using System.Collections.Generic;
using System.Numerics;
using ParserGenerator.CharacterMapping;
using ParserGenerator.Extensions;
using ParserGenerator.ScannerConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarAnalysis
{
    [Serializable]
    internal sealed class LexicalExpressionCompiler : ILexicalExpressionCompiler
    {
        public CharInterval Universal { get; }
        public ScannerAutomatonStartTable Starts { get; }
        public CharMap CharMap { get; private set; }

        private readonly List<LexicalDeclaration> _declarations;
        private readonly LexicalAliasTable _aliases;
        private readonly ScannerAutomatonItemTable _items;

        public LexicalExpressionCompiler(GrammarAnalyzer analyzer)
        {
            if (analyzer == null)
                throw new ArgumentNullException(nameof(analyzer));
            Universal = analyzer.Grammar.CharSetUniversal;
            Starts = analyzer.Starts;
            _declarations = new List<LexicalDeclaration>();
            _aliases = new LexicalAliasTable();
            _items = new ScannerAutomatonItemTable();
        }

        public ScannerAutomatonItemSetPack CompileLexicalExpression(LexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return n.AcceptLexicalExpressionCompiler(this);
        }

        public ScannerAutomatonItemSetPack CompileEmptyLexicalExpression(EmptyLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return new ScannerAutomatonItemSetPack();
        }

        private ScannerAutomatonItemSetPack CompileStringLiteral(StringLiteral n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = new ScannerAutomatonItemSetPack();
            ScannerAutomatonItem prev = null;
            foreach (int c in n.Value.EnumCodepoints())
            {
                int index = CharMap[c];
                CharClass symbol = CharMap.Classes[index];
                ScannerAutomatonItem item = _items.DefineShift(symbol);
                if (prev == null)
                    pack.FirstSet.Add(item);
                else
                    prev.FollowSet.Add(item);
                prev = item;
            }
            if (prev != null)
                pack.LastSet.Add(prev);
            if (pack.FirstSet.Count > 0)
                pack.Nullable = false;
            return pack;
        }

        public ScannerAutomatonItemSetPack CompileStringLexicalExpression(StringLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return CompileStringLiteral(n.Value);
        }

        public ScannerAutomatonItemSetPack CompileQuoteStringLexicalExpression(QuoteStringLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return CompileStringLiteral(n.Value);
        }

        public ScannerAutomatonItemSetPack CompileCharClassLexicalExpression(CharClassLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = new ScannerAutomatonItemSetPack();
            CharClass cc = n.Value.Evaluate(CharMap.Universal);
            foreach (int index in CharMap.Map[cc])
            {
                CharClass symbol = CharMap.Classes[index];
                ScannerAutomatonItem item = _items.DefineShift(symbol);
                pack.FirstSet.Add(item);
                pack.LastSet.Add(item);
            }
            if (pack.FirstSet.Count > 0)
                pack.Nullable = false;
            return pack;
        }

        public ScannerAutomatonItemSetPack CompileNameReferenceLexicalExpression(NameReferenceLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            LexicalAlias alias = _aliases.Lookup(n.Name.Name, n.Context);
            return CompileLexicalExpression(alias.Pattern);
        }

        public ScannerAutomatonItemSetPack CompileKleeneLexicalExpression(KleeneLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Operand);
            pack.Nullable = true;
            _items.UpdateFollowSet(pack.LastSet, pack.FirstSet);
            return pack;
        }

        public ScannerAutomatonItemSetPack CompileOptionalLexicalExpression(OptionalLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Operand);
            pack.Nullable = true;
            return pack;
        }

        public ScannerAutomatonItemSetPack CompilePositiveLexicalExpression(PositiveLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Operand);
            _items.UpdateFollowSet(pack.LastSet, pack.FirstSet);
            return pack;
        }

        public ScannerAutomatonItemSetPack CompileRepeatLexicalExpression(RepeatLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.MinCount == n.MaxCount)
                return Repeat(n.Operand, n.MinCount.Value, true);
            else if (n.MinCount == null)
                return Repeat(n.Operand, n.MaxCount.Value, false);
            else if (n.MaxCount == null)
            {
                ScannerAutomatonItemSetPack pack = Repeat(n.Operand, n.MinCount.Value, true);
                ScannerAutomatonItemSetPack pack2 = CompileLexicalExpression(n.Operand);
                pack2.Nullable = true;
                _items.UpdateFollowSet(pack2.LastSet, pack2.FirstSet);
                return Concat(pack, pack2);
            }
            else
            {
                ScannerAutomatonItemSetPack pack = Repeat(n.Operand, n.MinCount.Value, true);
                ScannerAutomatonItemSetPack pack2 = Repeat(n.Operand, n.MaxCount.Value - n.MinCount.Value, false);
                return Concat(pack, pack2);
            }
        }

        private ScannerAutomatonItemSetPack Repeat(LexicalExpression operand, BigInteger m, bool isExact)
        {
            if (operand == null)
                throw new ArgumentNullException(nameof(operand));
            if (m == 0)
                return new ScannerAutomatonItemSetPack();
            ScannerAutomatonItemSetPack pack = null;
            for (BigInteger i = 0; i < m; i++)
            {
                ScannerAutomatonItemSetPack pack2 = CompileLexicalExpression(operand);
                if (!isExact)
                    pack2.Nullable = true;
                if (pack == null)
                    pack = pack2;
                else
                    pack = Concat(pack, pack2);
            }
            if (pack == null)
                pack = new ScannerAutomatonItemSetPack();
            return pack;
        }

        private ScannerAutomatonItemSetPack Concat(ScannerAutomatonItemSetPack pack1, ScannerAutomatonItemSetPack pack2)
        {
            if (pack1 == null)
                throw new ArgumentNullException(nameof(pack1));
            if (pack2 == null)
                throw new ArgumentNullException(nameof(pack2));
            ScannerAutomatonItemSetPack pack = new ScannerAutomatonItemSetPack();
            pack.Nullable = pack1.Nullable && pack2.Nullable;
            pack.FirstSet.UnionWith(pack1.FirstSet);
            if (pack1.Nullable)
                pack.FirstSet.UnionWith(pack2.FirstSet);
            pack.LastSet.UnionWith(pack2.LastSet);
            if (pack2.Nullable)
                pack.LastSet.UnionWith(pack1.LastSet);
            _items.UpdateFollowSet(pack1.LastSet, pack2.FirstSet);
            return pack;
        }

        private ScannerAutomatonItemSetPack Union(ScannerAutomatonItemSetPack pack1, ScannerAutomatonItemSetPack pack2)
        {
            ScannerAutomatonItemSetPack pack = new ScannerAutomatonItemSetPack();
            pack.Nullable = pack1.Nullable || pack2.Nullable;
            pack.FirstSet.UnionWith(pack1.FirstSet);
            pack.FirstSet.UnionWith(pack2.FirstSet);
            pack.LastSet.UnionWith(pack1.LastSet);
            pack.LastSet.UnionWith(pack2.LastSet);
            return pack;
        }

        public ScannerAutomatonItemSetPack CompileConcatLexicalExpression(ConcatLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Left);
            ScannerAutomatonItemSetPack pack2 = CompileLexicalExpression(n.Right);
            return Concat(pack, pack2);
        }

        public ScannerAutomatonItemSetPack CompileUnionLexicalExpression(UnionLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Left);
            ScannerAutomatonItemSetPack pack2 = CompileLexicalExpression(n.Right);
            return Union(pack, pack2);
        }

        private void CompileLexicalRuleDeclarator(LexicalRuleDeclarator n, HashSet<ScannerAutomatonStart> starts)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (starts == null)
                throw new ArgumentNullException(nameof(starts));
            ScannerAutomatonItemSetPack pack = CompileLexicalExpression(n.Pattern);
            ScannerAutomatonItem reduce = _items.DefineReduction(n);
            _items.UpdateFollowSet(pack.LastSet, reduce);
            foreach (ScannerAutomatonStart start in starts)
            {
                start.Items.UnionWith(pack.FirstSet);
                if (!n.AtLineBegin)
                    start.StrictItems.UnionWith(pack.FirstSet);
            }
        }

        private void CompileLexicalStartDeclarator(LexicalStartDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            HashSet<ScannerAutomatonStart> starts = Starts.Resolve(n.Starts);
            foreach (LexicalRuleDeclarator rule in n.Rules)
                CompileLexicalRuleDeclarator(rule, starts);
            if (n.DefaultAction != null)
                foreach (ScannerAutomatonStart start in starts)
                    start.SetDefaultAction(n.DefaultAction);
        }

        private void CompileLexicalDeclaration(LexicalDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (LexicalDeclarator item in n.Declarators)
                if (item.IsStart)
                    CompileLexicalStartDeclarator(item.AsStart.Declarator);
        }

        public void AddLexicalDeclaration(LexicalDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            _declarations.Add(n);
        }

        private void DefineAliases()
        {
            foreach (LexicalDeclaration item in _declarations)
                _aliases.Define(item);
            _aliases.Simplify();
            _aliases.Check();
        }

        private void ScanCharClasses()
        {
            CharClassCollector collector = new CharClassCollector(_aliases, Universal);
            collector.ProcessLexicalAliases();
            foreach (LexicalDeclaration item in _declarations)
                collector.ProcessLexicalDeclaration(item);
            CharMap = collector.CharMap.Build();
        }

        private void CompileLexicalRules()
        {
            foreach (LexicalDeclaration item in _declarations)
                CompileLexicalDeclaration(item);
        }

        public void Compile()
        {
            DefineAliases();
            ScanCharClasses();
            CompileLexicalRules();
        }
    }
}
